package edu.uic.cs.cs582;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.sat4j.specs.TimeoutException;

import edu.mit.jwi.item.POS;
import edu.uic.cs.cs582.data.Component;
import edu.uic.cs.cs582.data.Synset;
import edu.uic.cs.cs582.data.SynsetSet;
import edu.uic.cs.cs582.data.Word;
import edu.uic.cs.cs582.misc.ClassFactory;
import edu.uic.cs.cs582.misc.Config;
import edu.uic.cs.cs582.misc.GeneralException;
import edu.uic.cs.cs582.misc.LogHelper;
import edu.uic.cs.cs582.process.LogicFormulaProcessor;
import edu.uic.cs.cs582.process.SatProcessor;
import edu.uic.cs.cs582.process.WordsComponentBuilder;
import edu.uic.cs.cs582.process.impl.SatIndexParser.Index;
import edu.uic.cs.cs582.process.impl.SatProcessorImplKeepingMiddleVariables;
import edu.uic.cs.cs582.process.impl.WordsComponentBuilderImpl;

public class Main
{

	private static final Logger LOGGER = LogHelper.getLogger(Main.class);

	//	private static final ArrayList<String> OUTPUT_LINES = new ArrayList<String>();
	private static final String INPUT_FOLDER_NAME = "input";

	private static final String INPUT_INFO_FILE = "inputFiles.properties";

	private static OutputStream OUTPUTSTREAM = null;

	public static void main(String[] args)
	{
		try
		{
			preExecute();

			Properties inputFiles = readInputFilesInfomation();
			List<Entry<Object, Object>> inputFilesList = sortFileNames(inputFiles);

			// process each file
			for (Entry<Object, Object> entry : inputFilesList)
			{
				String fileName = entry.getKey().toString();
				POS pos = POS.valueOf(entry.getValue().toString()
						.toUpperCase(Locale.US));

				processOneFileOfCertainPos(fileName, pos);
			}
		}
		finally
		{
			postExecute();
		}
	}

	private static void preExecute()
	{
		File outputFile = new File(Config.OUTPUT_FILE_PATH);
		try
		{
			OUTPUTSTREAM = FileUtils.openOutputStream(outputFile);
		}
		catch (IOException ioe)
		{
			throw new GeneralException(ioe);
		}
	}

	/**
	 * sort files in alphabetic order
	 * 
	 * @param inputFiles
	 * @return
	 */
	private static List<Entry<Object, Object>> sortFileNames(
			Properties inputFiles)
	{
		List<Entry<Object, Object>> inputFilesList = new ArrayList<Entry<Object, Object>>(
				inputFiles.entrySet());
		Collections.sort(inputFilesList,
				new Comparator<Entry<Object, Object>>()
				{
					@Override
					public int compare(Entry<Object, Object> one,
							Entry<Object, Object> other)
					{
						String oneInString = one.getKey().toString()
								+ one.getValue().toString()
										.toUpperCase(Locale.US);
						String otherInString = other.getKey().toString()
								+ other.getValue().toString()
										.toUpperCase(Locale.US);
						return oneInString.compareTo(otherInString);
					}
				});

		return inputFilesList;
	}

	/**
	 * read files' information, 
	 * this file {@link #INPUT_INFO_FILE} contains the files need to check, 
	 * and the words' POS in the file
	 * 
	 * @return
	 */
	private static Properties readInputFilesInfomation()
	{
		Properties inputFiles = new Properties();
		InputStream inStream = ClassLoader
				.getSystemResourceAsStream(INPUT_INFO_FILE);

		try
		{
			inputFiles.load(inStream);
		}
		catch (IOException e)
		{
			throw new GeneralException(e);
		}

		return inputFiles;
	}

	private static void postExecute()
	{
		IOUtils.closeQuietly(OUTPUTSTREAM);
	}

	private static void println(String line)
	{
		System.out.println(line);
		//		OUTPUT_LINES.add(line);

		try
		{
			IOUtils.write(line, OUTPUTSTREAM);
			IOUtils.write(IOUtils.LINE_SEPARATOR, OUTPUTSTREAM);
		}
		catch (IOException e)
		{
			LOGGER.error("Catch IOException while write line to output file. ",
					e);
		}
	}

	private static void println()
	{
		println("");
	}

	private static void processOneFileOfCertainPos(String fileName, POS pos)
	{
		println("Input file [" + fileName + "]. ");

		String inputPath = Main.class.getClassLoader().getResource(".")
				.getFile()
				+ File.separator
				+ INPUT_FOLDER_NAME
				+ File.separator
				+ fileName;

		WordsComponentBuilder componentBuilder = new WordsComponentBuilderImpl(
				Config.WORDNET_PATH);
		println("Begin to bulid components for [" + pos
				+ "], please stand by... ");

		// build the components for each file
		List<Component> components = componentBuilder.buildComponents(
				inputPath, pos);
		println("End to bulid components for [" + pos + "]. Total ["
				+ components.size() + "] components. ");
		println("Begin to process each component, please stand by... ");
		println();
		println("=====================================");

		Set<String> allConflictWords = new HashSet<String>();

		//process each componment
		Component component = null;
		for (int componentCount = 1; componentCount <= components.size(); componentCount++)
		{
			component = components.get(componentCount - 1);
			processEachComponent(pos, componentCount, component,
					allConflictWords, false);
		}

		println("End to process all components. Total ["
				+ allConflictWords.size() + "] inconsistent words. ");
		LOGGER.warn("##################################################################################################");
		for (String wordString : allConflictWords)
		{
			println(wordString);
		}
		println();
	}

	private static void processEachComponent(POS pos, int componentCount,
			Component component, Set<String> allConflictWords,
			boolean isRecursive)
	{
		LogicFormulaProcessor formulaProcessor = ClassFactory
				.getInstance(Config.LOGIC_FORMULA_PROCESSOR_CLASS_NAME);

		SatProcessor satProcessor = new SatProcessorImplKeepingMiddleVariables(
				component);

		try
		{
			// generate CNF for each component
			Map<int[], Index> indicesByCnfClause = formulaProcessor
					.generateCNF(component, pos);

			//			// convert CNF into int[] which is the required format by SAT4j
			//			int[][] cnfArrays = formulaProcessor
			//					.convertCnfIntoIntArrays(cnfClausesByIndex.keySet());

			// use SAT4j solve CNF to check if there is inconsistency
			boolean result = satProcessor.processSat(indicesByCnfClause);
			if (!result)
			{
				if (!isRecursive)
				{
					println("**** Inconsistency detected! - " + component);
				}

				printOutInconsistenciesForEachComponent(allConflictWords,
						satProcessor);

				Set<Word> certainlyInconsistentWords = getInconsistentWordsWhoseAllMajoritySynsetsAreInconsistent(
						component, satProcessor);
				if (!certainlyInconsistentWords.isEmpty())
				{
					component = component.clone(certainlyInconsistentWords);
					processEachComponent(pos, componentCount, component,
							allConflictWords, true);

				}

				if (!isRecursive)
				{
					println("=====================================");
				}
			}
		}
		catch (TimeoutException e)
		{
			println("[" + componentCount
					+ "] Time out, we cannot solve this component - "
					+ component);
			println("=====================================");
		}
	}

	private static Set<Word> getInconsistentWordsWhoseAllMajoritySynsetsAreInconsistent(
			Component component, SatProcessor satProcessor)
	{
		Set<Word> result = new TreeSet<Word>();

		Map<Synset, Set<Word>> conflictWordsBySynset = satProcessor
				.getConflictWordsBySynset();
		Map<SynsetSet, Set<Word>> conflictWordsByMajoritySynsets = satProcessor
				.getConflictWordsBySynsetsInMajority();

		Set<Word> allConflictWords = new HashSet<Word>();
		Set<Synset> allConflictSynsets = new HashSet<Synset>();
		fetchAllConflictWordsAndSynsets(conflictWordsBySynset,
				conflictWordsByMajoritySynsets, allConflictWords,
				allConflictSynsets);

		for (Word eachConflictWord : allConflictWords)
		{
			if (allConflictSynsets.containsAll(eachConflictWord
					.getAllSynsetsInMajoritySynsets()))
			{
				result.add(eachConflictWord);
			}
		}

		return result;
	}

	private static void fetchAllConflictWordsAndSynsets(
			Map<Synset, Set<Word>> conflictWordsBySynset,
			Map<SynsetSet, Set<Word>> conflictWordsByMajoritySynsets,
			Set<Word> allConflictWords, Set<Synset> allConflictSynsets)
	{
		for (Entry<Synset, Set<Word>> entry : conflictWordsBySynset.entrySet())
		{
			allConflictWords.addAll(entry.getValue());
			allConflictSynsets.add(entry.getKey());
		}

		for (Entry<SynsetSet, Set<Word>> entry : conflictWordsByMajoritySynsets
				.entrySet())
		{
			allConflictWords.addAll(entry.getValue());
			allConflictSynsets.addAll(entry.getKey());
		}
	}

	private static void printOutInconsistenciesForEachComponent(
			Set<String> allConflictWords, SatProcessor satProcessor)
	{
		Map<SynsetSet, Set<Word>> conflictWordsByMajoritySynsets = satProcessor
				.getConflictWordsBySynsetsInMajority();
		for (Map.Entry<SynsetSet, Set<Word>> entry : conflictWordsByMajoritySynsets
				.entrySet())
		{
			String each = ">>>>>>>>* ";
			for (Word word : entry.getValue())
			{
				each += word.getWord() + "[" + word.getPolarity() + "] ";
				allConflictWords.add(word.getWord());
			}
			println(each + " -- " + entry.getKey());
		}

		///////////////////
		Map<Synset, Set<Word>> conflictWordsBySynset = satProcessor
				.getConflictWordsBySynset();
		for (Map.Entry<Synset, Set<Word>> entry : conflictWordsBySynset
				.entrySet())
		{
			String each = ">>>>>>>>  ";
			boolean hasWordsToDisplay = false;
			for (Word word : entry.getValue())
			{
				if (!allConflictWords.contains(word.getWord()))
				{
					each += word.getWord() + "[" + word.getPolarity() + "] ";
					hasWordsToDisplay = allConflictWords.add(word.getWord());
				}
			}

			if (hasWordsToDisplay)
			{
				println(each + " -- (" + entry.getKey() + ") ");
			}
		}
	}
}
